package com.xy.tool.data.redis.ex;

import com.xy.lang.domain.LongHolder;
import com.xy.lang.util.CollectionUtilsEx;
import com.xy.tool.data.redis.util.RedisUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.xy.tool.data.redis.util.TimeUtils.getExpiredSecondsVariation;

public class RedisTemplateEx<V> {

    public final static int DEFAULT_MULTI_OPERATION_BATCH_SIZE = 100;

    private final RedisTemplate<String, V> redisTemplate;
    private final ValueOperationsEx<V> valueOpsEx;

    public RedisTemplateEx(RedisTemplate<String, V> redisTemplate) {
        Objects.requireNonNull(redisTemplate);
        this.redisTemplate = redisTemplate;
        this.valueOpsEx = new ValueOperationsEx<>(redisTemplate);
    }

    public ValueOperationsEx<V> opsForValue() {
        return valueOpsEx;
    }

    public final Long delete(Collection<String> keys) {
        return delete(keys, DEFAULT_MULTI_OPERATION_BATCH_SIZE);
    }

    public final Long delete(Collection<String> keys, int batchSize) {
        if(CollectionUtils.isEmpty(keys)) {
            return 0L;
        }
        if(batchSize < 1) {
            batchSize = DEFAULT_MULTI_OPERATION_BATCH_SIZE;
        }
        if(keys.size() > batchSize) {
            LongHolder count = new LongHolder(0L);
            CollectionUtilsEx.foreachSubList(new ArrayList<>(keys),
                    batchSize,
                    (loop, startIndex, endIndex, subList) -> {
                        long delta = redisTemplate.delete(subList);
                        count.increment(delta);
                    });
            return count.get();
        }
        return redisTemplate.delete(keys);
    }

    public final Boolean expire(String key, long seconds) {
        if(!StringUtils.hasText(key)) {
            return false;
        }
        return redisTemplate.expire(key,
                seconds + getExpiredSecondsVariation(seconds),
                TimeUnit.SECONDS);
    }

    public final void expire(Collection<String> keys, long seconds) {
        expire(keys, seconds, DEFAULT_MULTI_OPERATION_BATCH_SIZE);
    }

    public final void expire(Collection<String> keys, long seconds, int batchSize) {
        if (CollectionUtils.isEmpty(keys)) {
            return;
        }
        if (batchSize < 1) {
            batchSize = DEFAULT_MULTI_OPERATION_BATCH_SIZE;
        }
        if(seconds < 1) {
            return;
        }
        if (keys.size() > batchSize) {
            CollectionUtilsEx.foreachSubList(new ArrayList<>(keys),
                    batchSize,
                    (loop, startIndex, endIndex, subList) -> {
                        RedisUtils.expire(redisTemplate, subList, seconds);
                    });
        } else {
            RedisUtils.expire(redisTemplate, keys, seconds);
        }
    }

    public final void expireAt(Collection<String> keys, Date date) {
        expireAt(keys, date, DEFAULT_MULTI_OPERATION_BATCH_SIZE);
    }

    public final void expireAt(Collection<String> keys, Date date, int batchSize) {
        if (CollectionUtils.isEmpty(keys)) {
            return;
        }
        if (batchSize < 1) {
            batchSize = DEFAULT_MULTI_OPERATION_BATCH_SIZE;
        }
        if(date.getTime() <= System.currentTimeMillis()) {
            return;
        }
        if(keys.size() > batchSize) {
            CollectionUtilsEx.foreachSubList(new ArrayList<>(keys),
                    batchSize,
                    (loop, startIndex, endIndex, subList) -> {
                        RedisUtils.expireAt(redisTemplate, subList, date);
                    });
        } else {
            RedisUtils.expireAt(redisTemplate, keys, date);
        }
    }

    public Set<String> getKeys(String pattern, int scanSize) {
        if(scanSize < 1) {
            throw new IllegalArgumentException("The scanSize should be than 0.");
        }
        String pattern0 = pattern == null ? "" : pattern;
        return redisTemplate.execute(new RedisCallback<Set<String>>() {
            @Override
            public Set<String> doInRedis(RedisConnection connection) throws DataAccessException {
                Set<String> keys = new HashSet<>();
                Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder()
                        .match(pattern0.concat("*"))
                        .count(scanSize)
                        .build());
                String key;
                while (cursor.hasNext()) {
                    key = new String(cursor.next());
                    if(StringUtils.hasText(key)) {
                        keys.add(key);
                    }
                }
                return keys;
            }
        });
    }

}
